use serde_json::{from_str, from_value, Value};
use std::collections::HashMap;

pub fn check_id_card(id_card_no: &str) -> String {
    // https://github.com/ionepub/idcard/blob/master/res/region.json
    let region = std::fs::read_to_string("region.json").unwrap();
    let region: Value = from_str(&region).unwrap();
    let region: HashMap<String, String> = from_value(region).unwrap();
    let err = "身份证号码错误";

    if !validate_len(id_card_no) {
        return err.to_string();
    }

    if !validate_char(id_card_no) {
        return err.to_string();
    }

    let birthday_str = match validate_birthday(id_card_no) {
        Ok(birthday) => birthday,
        Err(_) => {
            return err.to_string();
        }
    };

    if !validate_checksum(id_card_no) {
        return err.to_string();
    }

    let province_str = match validate_province(id_card_no, &region) {
        Ok(province) => province,
        Err(_) => {
            return err.to_string();
        }
    };

    let city_str = match validate_city(id_card_no, &region) {
        Ok(city) => city,
        Err(_) => {
            return err.to_string();
        }
    };

    let district_str = match validate_district(id_card_no, &region) {
        Ok(district) => district,
        Err(_) => {
            return err.to_string();
        }
    };

    let gender_str = get_gender(id_card_no);

    format!(
        "身份证号码正确,{},{},{}-{}-{}",
        gender_str, birthday_str, province_str, city_str, district_str
    )
}

fn get_days(year: u32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if judge_leap(year) {
                29
            } else {
                28
            }
        }
        _ => 0,
    }
}

fn judge_leap(year: u32) -> bool {
    if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
        true
    } else {
        false
    }
}

fn validate_len(id: &str) -> bool {
    id.len() == 18 || id.len() == 15
}

fn validate_char(id: &str) -> bool {
    if id.len() == 15 {
        return id.chars().all(|c| c.is_digit(10));
    } else {
        let mut chars = id.chars();
        for _ in 0..17 {
            if !chars.next().unwrap().is_digit(10) {
                return false;
            }
        }
        let last_char = chars.next().unwrap();
        if !last_char.is_digit(10) && last_char.to_ascii_uppercase() != 'X' {
            return false;
        } else {
            return true;
        }
    }
}

fn validate_birthday(id: &str) -> Result<String, ()> {
    let (year, month, day) = if id.len() == 18 {
        (
            *&id[6..10].parse::<u32>().unwrap(),
            *&id[10..12].parse::<u32>().unwrap(),
            *&id[12..14].parse::<u32>().unwrap(),
        )
    } else {
        (
            *&id[6..8].parse::<u32>().unwrap() + 1900,
            *&id[8..10].parse::<u32>().unwrap(),
            *&id[10..12].parse::<u32>().unwrap(),
        )
    };

    if year < 1800 || year > 2025 {
        return Err(());
    }

    if month < 1 || month > 12 {
        return Err(());
    }

    let days_in_month = get_days(year, month);

    if day < 1 || day > days_in_month {
        return Err(());
    }

    Ok(format!("{}年{:02}月{:02}日", year, month, day))
}

fn validate_checksum(id: &str) -> bool {
    if id.len() == 15 {
        return true;
    }
    let weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    let check_code = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

    let mut sum = 0;
    let mut last = ' ';
    let chars = id.chars();
    for (i, c) in chars.enumerate() {
        if i < 17 {
            let digit = c.to_digit(10).unwrap();
            sum += digit * weights[i];
        } else {
            last = c;
        }
    }

    let expected = check_code[(sum % 11) as usize];

    last = if last == 'x' {
        last.to_ascii_uppercase()
    } else {
        last
    };

    expected == last
}

fn validate_province(id: &str, region: &HashMap<String, String>) -> Result<String, ()> {
    let province_code = &id[0..2];
    let province_code = format!("{}0000", province_code);
    match region.get(&province_code) {
        Some(province) => {
            if province != " " {
                Ok(province.clone())
            } else {
                Err(())
            }
        }
        None => Err(()),
    }
}

fn validate_city(id: &str, region: &HashMap<String, String>) -> Result<String, ()> {
    let city_code = &id[0..4];
    let city_code = format!("{}00", city_code);
    match region.get(&city_code) {
        Some(city) => {
            if city != " " {
                Ok(city.clone())
            } else {
                Err(())
            }
        }
        None => Err(()),
    }
}

fn validate_district(id: &str, region: &HashMap<String, String>) -> Result<String, ()> {
    let district_code = &id[0..6];
    match region.get(district_code) {
        Some(district) => {
            if district != " " {
                Ok(district.clone())
            } else {
                Err(())
            }
        }
        None => Err(()),
    }
}

fn get_gender(id: &str) -> String {
    let idx = if id.len() == 15 { 14 } else { 16 };

    let c = id.chars().nth(idx).unwrap().to_digit(10).unwrap();
    if c % 2 == 0 {
        "女".to_string()
    } else {
        "男".to_string()
    }
}
